package org.semanticflash.rdf.triple
{
	

	import com.bigflexlib.core.markers.IValueSingleton;
	import com.bigflexlib.ds.container.utils.ContainerUtil;
	import com.bigflexlib.ds.container.zet.IRSet;
	import com.bigflexlib.ds.container.zet.IRWSet;
	import com.bigflexlib.ds.container.zet.impl.RWDictionarySet;
	
	import flash.utils.Dictionary;
	
	import org.semanticflash.rdf.values.IRI;
	import org.semanticflash.rdf.values.RDFValue;


	/**
	 * 
	 * RDF Triples are use to both build triples and triple masks.
	 * This design decision allows us to reuse indexing and caching for triples and masks
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */
	public class RDFTriple implements IValueSingleton
	{
		
		
		/**
		 * The all wildcard triple ( *, *, * ). Provided as a constant to speed up
		 * access. 
		 * 
		*/		
		public static const ALL:RDFTriple = RDFTriple.instance( null, null, null );
		
		
		
		

		private var _s:RDFValue;
		public function get s( ):RDFValue
		{
			return _s;
		}


		private var _p:IRI;
		public function get p( ):IRI
		{
			return _p;
		}


		private var _o:RDFValue;
		public function get o( ):RDFValue
		{
			return _o;
		}
		






		private var _isGrounded:Boolean = true;
		public function get isGrounded( ):Boolean
		{
			return _isGrounded;		
		}
				
		
		
		private var _hasWildCard:Boolean = false;
		public function get hasWildCard( ):Boolean
		{
			return _hasWildCard;
		}
		
		
		
		

		public function RDFTriple( s:RDFValue, p:IRI, o:RDFValue )
		{

			_s = s;

			_p = p;

			_o = o;		



			// calculate this now and store	
				
			if ( s === IRI.WILDCARD )
				_hasWildCard = true;
			else if ( p === IRI.WILDCARD )
				_hasWildCard = true;
			else if ( o === IRI.WILDCARD )
				_hasWildCard = true;
				
				
				
			if ( isBNode( s ) )
				_isGrounded = false;
			else if ( isBNode( p )  )
				_isGrounded = false;
			else if ( isBNode( o )  )
				_isGrounded = false;				
				
				

		}
		
		
		
		
		private function isBNode( v:RDFValue ):Boolean
		{
			if ( ! (v is IRI) )
				return false;
			return ( v as IRI ).isBNode;
		}
		
		
		
		
		
		
		
		
		

		
		/**
		 * 
		 * A triple where s and o have been reversed.
		 * This is sometimes useful when building queries.
		 * 
		 */		
		public function get inverse( ):RDFTriple
		{
			return 	RDFTriple.instance( _o, _p, _s );	
		}

		
		
		

		
		
		
		
		/**
		 * 
		 * Returns a triple where the terms that are false are replaced by IRI.WILDCARDs.
		 * Allows you to build a mask using one ore more terms from this triple.
		 * 
		 * 
		 * @param s
		 * @param p
		 * @param o
		 * @return 
		 * 
		 */		
		public function getMask( s:Boolean = false, p:Boolean = false, o:Boolean = false ):RDFTriple
		{
			var w:IRI = IRI.WILDCARD;
			return RDFTriple.instance( s?_s:w , p?_p:w , o?_o:w );
		}
		
		
		
		
		
		/**
		 * 
		 * Returns several masks as specified by specs, a two dimensional array where each row
		 * specifies the masking values for s, p and o in that order.
		 * Internally it calls getMask once for each row.
		 * You can use true/false or 1/0 to specify the masks
		 * 
		 * @param specs
		 * @return 
		 * 
		 */		
		public function getMasks( specs:Array ):IRSet
		{
			
			var masks:IRWSet = new RWDictionarySet( );
			
			for each ( var row:Array in specs )
			{
				masks.add( getMask( Boolean( row[0] ), Boolean( row[1] ), Boolean( row[2] ) ) );
			}
			
			return masks;
		
		}
		
		
		
		
		
		
		
		
		
		
		/////////////////////////////////////////////////////////////////
		// Containment
		/////////////////////////////////////////////////////////////////
		
		
		public static const CONTAINS			:int =  1;
		public static const IS_CONTAINED		:int = -1;
		public static const NONE				:int =  0;
		
		
		
		/**
		 * 
		 * Returns true if fixed terms in this triple match those of the other triple.
		 * This algorithm is the basis of pattern matching.
		 * 
		 * <ul>
		 * 		<li>(a,*,c).contains(a,b,c) = true</li>
		 * 		<li>(a,*,c).contains(a,d,c) = true</li>
		 * 		<li>(a,*,c).contains(a,*,c) = true</li>
		 * 		<li>(a,b,c).contains(a,b,c) = true</li>
		 * 		<li>(a,*,c).contains(a,b,d) = false</li>
		 * </ul>
		 * 
		 * @param otherTriple
		 * @return 
		 * 
		 */		
		public function contains( otherTriple:RDFTriple ):Boolean
		{
			
			if ( _s !== IRI.WILDCARD )
				if ( _s !== otherTriple.s )
					return false;
					
			if ( _p !== IRI.WILDCARD )
				if ( _p !== otherTriple.p )
					return false;
					
			if ( _o !== IRI.WILDCARD )
				if ( _o !== otherTriple.o )
					return false;
		
			return true;
		}		
		
		
		
		/**
		 * 
		 * 
		 * 
		 * @param otherTriple
		 * @return one of ( CONTAINS, IS_CONTAINED, NONE )
		 * 
		 */
		public function getContainmentRelation( otherTriple:RDFTriple ):int
		{
		
			if ( contains( otherTriple ) )
				return CONTAINS;
			
			if ( otherTriple.contains( this ) )
				return IS_CONTAINED;
			
			return NONE;

		}
		
		
		
		
		/**
		 * 
		 * Used by keepMostGeneral and keepMostSpecific
		 * 
		 * @param triples
		 * @param containmentRelationToRemove
		 * @return 
		 * 
		 */	
		private static function optimize( tripleSet:IRSet, containmentRelationToRemove:int = CONTAINS ):IRSet
		{
			
			var triples:Array = ContainerUtil.toArray( tripleSet );

			var i:int, j:int, l:int = triples.length;
			
			var marked:Array = [ ];
			
			
			for ( i=0; i<l; i++ )
			{
				for ( j=0; j<l; j++ )
				{
					
					if ( i == j )
						continue;
					
					var t1:RDFTriple = triples[ i ];
					var t2:RDFTriple = triples[ j ];
					
					var cr:int = t1.getContainmentRelation( t2 );
					
					if ( cr == containmentRelationToRemove )
						marked[ i ] = true;
				}
				
			}
		
			
			var result:IRWSet = new RWDictionarySet( );
			
			for ( i=0; i<l; i++ )
				if ( ! marked[ i ] )
					result.add( triples[ i ] );
		
			return result;
		
		}
		
		
		
		/**
		 * 
		 * Given a set of triples will remove those that are contain()ed by others in the set.
		 * 
		 *  <pre>
		 * 
		 * Input:
		 *  (a,b,c)
		 *  (a,*,*)
		 *  (a,*,c)
		 *  (x,y,z)
		 * 
		 * Output:
		 * (a,*,*)
		 * (x,y,z)
		 *  
		 * </pre>
		 * 
		 * @param triples
		 * @return a new array with the result
		 * 
		 */		
		public static function keepMostGeneral( tripleSet:IRSet ):IRSet
		{
			return optimize( tripleSet, IS_CONTAINED );
		}
		
		

		/**
		 * 
		 * Given a set of triples will remove those that contain() others in the set
		 * 
		 *  <pre>
		 * 
		 * Input:
		 *  (a,b,c)
		 *  (a,*,*)
		 *  (a,*,c)
		 *  (x,y,z)
		 * 
		 * Output:
		 * (a,b,c)
		 * (x,y,z)
		 *  
		 * </pre>
		 * 
		 * @param triples
		 * @return a new array with the result
		 * 
		 */			
		public static function keepMostSpecific( tripleSet:IRSet ):IRSet
		{
			return optimize( tripleSet, CONTAINS );
		}
		
		
		/////////////////////////////////////////////////////
		// Serialization
		/////////////////////////////////////////////////////



		private var _nt:String;

		/**
		 * 
		 * Returns triple in expanded NTriple format.
		 * No punctuation is added at the end.
		 * 
		 * @return 
		 * 
		 */
		public function get nt( ):String
		{
			if ( _nt == null )
				_nt = $nt;
			return _nt;
		}

		
		
		private function get $nt( ):String
		{
			return ( [ _s.nt, _p.nt, _o.nt ] as Array ).join( " " );
		}
		
		
		
		public function toString( ):String
		{
			return nt;
		}
		
		
		
		
		
		
		private var _ntNoBNodes:String;
		
		/**
		 * 
		 * Returns triple in expanded NTriple format.
		 * Any BNode is replaced with an underscore ( _ ).
		 * 
		 * @return 
		 * 
		 */		
		public function get ntNoBNodes( ):String
		{
			if ( _ntNoBNodes == null )
				_ntNoBNodes = $ntNoBNodes;
			return _ntNoBNodes;
		}
		

		private function get $ntNoBNodes( ):String
		{
			return ( [ resolveNTNoBNode(_s), resolveNTNoBNode(_p), resolveNTNoBNode(_o) ] as Array ).join( " " );
		}







		private function resolveNTNoBNode( v:RDFValue ):String
		{
			if ( (v is IRI) && (v as IRI).isBNode )
				return "_";
			return v.nt;		
		}


		
		
		
		/////////////////////////////////////////////////////
		// Factories
		/////////////////////////////////////////////////////
		
		
		private static var cache:Object;
		

		public static function instance( s:RDFValue, p:IRI, o:RDFValue ):RDFTriple
		{
			
			if ( !cache ) 
			{
				// we instantiate here instead of the static block
				// because some static vars may call this func
				// before static instantiation happens
				cache = {};
			}
			
			
			
			if ( s == null ) s = IRI.WILDCARD;
			if ( p == null ) p = IRI.WILDCARD;
			if ( o == null ) o = IRI.WILDCARD;
			
			var key:String = s.uid + "_" + p.uid + "_" + o.uid ;
			if ( ! cache[ key ] )
			{
				var t:RDFTriple = new RDFTriple( s, p, o );
				cache[ key ] = t;
				return t; 										
			}
			return cache[ key ] as RDFTriple;
		}
		
		
	
		/**
		 * 
		 * From an array with the terms in s, p, o order.
		 * Can contain NULLs ( will be replaced by IRI.WILDCARDs )
		 * 
		 * @param terms
		 * @return 
		 * 
		 */		
		public static function instance2( terms:Array ):RDFTriple
		{
			return instance( t( terms[0] ), t( terms[1] ) as IRI, t( terms[2] ) );		
		}
		

		private static function t( term:* ):RDFValue
		{
			if ( term == null )
				return IRI.WILDCARD;
			return term as RDFValue;
		}		
		
		
		

		/**
		 * 
		 * Sometimes it is easier to declare triples using a dictionary structure.
		 * 
		 * <pre>
		 * var d:Dictionary = new Dictionary( );
		 * d[ org.semanticflash.rdf.values.IRI ] = subjectIRI;
		 * d[ predicate1 ] = object1;
		 * d[ predicate2 ] = object2;
		 * </pre>
		 * 
		 * 
		 * @param dict
		 * @return an array containing all the triples that were extracted from the dictionary structure
		 * 
		 */		
		public static function fromDictionary( dict:Dictionary ):Array
		{
			var triples:Array = [ ];
			var s:IRI = dict[ IRI ] as IRI;
			for ( var p:* in dict )
				if ( p is IRI )
					triples.push( instance( s, p as IRI, dict[ p ] as RDFValue ) );
			return triples;	
		}
		
		
		
		
		
		
		
		public static function toNT( triples:Array ):String
		{
		
			var lines:Array = [];
			for each ( var triple:RDFTriple in triples )
			{
				lines.push( triple.nt );
			}
		
			return lines.join( " .\n" ) + " .\n";
			
		}
		
		
		
		
		
		
		
		
		
		
		
		/**
		 * 
		 * TODO: not finished
		 * 
		 * Attempts to parse serialized triples in NTriple syntax and returns null if it fails.
		 * 
		 * 
		 * @param str
		 * 
		 * @param prefixMap
		 * 		An object whose keys are strings ( pnames including the colons: "foaf:", "rdf:", ":" ) 
		 * 		and whose values are the corresponding namespace IRIs ( as IRI instances )
		 * 
		 * @return 
		 * 
		 */		
		public static function fromNT( str:String, prefixMap:Object = null ):Array
		{
		
			if ( prefixMap == null )
				prefixMap = { };

			
			// tokenize string
						
			
			// TODO: refactor some logic out of NTripleParser
			
					
			
	
	
			return null;					
		
		
		
		}
		
		
		
		
		
		
		
		
		
		
		
		
		
	}


}

